package com.dmbjz.diners.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.dmbjz.common.constant.ApiConstant;
import com.dmbjz.common.constant.PointTypesConstant;
import com.dmbjz.common.exception.ParameterException;
import com.dmbjz.common.model.domain.ResultInfo;
import com.dmbjz.common.model.vo.SingInDinerInfo;
import com.dmbjz.common.utils.AssertUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.time.LocalDateTime;
import java.util.*;


/*用户签到服务*/
@Service
@Transactional(rollbackFor = Exception.class)
public class SignService {

    @Value("${service.name.ms-oauth-service}")
    private String oauthServiceName;
    @Value("${service.name.ms-points-service}")
    private String pointsServerName;

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RestTemplate restTemplate;


    /*用户签到并返回签到积分*/
    public int doSign(String accessToken,String dateStr){

        SingInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);
        Date date = getDate(dateStr);
        int offset= DateUtil.dayOfMonth(date)-1;                            //获取日期对应的天数，Redis开始索引是0，需要-1
        String key = buildSignKey(dinerInfo.getId(),date);                  //根据日期创建Key
        Boolean isSign = redisTemplate.opsForValue().getBit(key, offset);   //获取当天是否已经签到
        AssertUtil.isTrue(isSign,"请不要重复签到!");
        redisTemplate.opsForValue().setBit(key,offset,true);          //进行签到
        int count = getSignCount(dinerInfo.getId(),date);                  //统计连续签到的次数
        return addPoints(count, dinerInfo.getId());                        //添加签到积分

    }
    /*统计连续签到的次数*/
    private int getSignCount(Integer dinerId, Date date) {

        /*获取当前日期的天数*/
        int dayOfMonth = DateUtil.dayOfMonth(date);
        String signKey = buildSignKey(dinerId, date);

        /*相当于 bitfield key get u当前天数 0 */
        BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                .valueAt(0);
        List<Long> list = redisTemplate.opsForValue().bitField(signKey, bitFieldSubCommands);
        if(CollectionUtil.isEmpty(list)){
            return 0;
        }
        int singCount = 0;//连续签到次数
        long v = list.get(0) == null?0:list.get(0);//非空判断2
        /*将二进制数转为10101010的源数据*/
        for (int i = dayOfMonth;i>0;i--){
            //数据右移再左移与当前数据一致，说明最后一位为0，未签到
            if( v>>1<<1 ==v ){
                //低位是0且非当天连续签到中断
                if( i!=dayOfMonth )
                    break;
            }else{
                singCount++;
            }
            v>>=1;//二进制日期右移一位，缩短一天
        }

        return singCount  ;
    }
    /*根据用户Id和传入时间转换为Key*/
    private String buildSignKey(Integer dinerId,Date date) {
        return String.format("user:sign:%d:%s",dinerId,DateUtil.format(date,"yyyyMM"));
    }
    /*根据字符串获取日期*/
    private Date getDate(String dateStr) {
        if(StrUtil.isBlank(dateStr)){
            return new Date();
        }
        try {
            return DateUtil.parseDate(dateStr);
        } catch (Exception e) {
            throw new RuntimeException("请传入yyyy-MM-dd格式的日期");
        }
    }
    /*获取登录用户的信息*/
    public SingInDinerInfo loadSignInDinerInfo(String accessToken){

        AssertUtil.mustLogin(accessToken);
        String url = oauthServiceName+"user/me?access_token={accessToken}";
        ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);
        if(resultInfo.getCode()!= ApiConstant.SUCCESS_CODE){
            throw new ParameterException(resultInfo.getMessage());
        }
        SingInDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap)resultInfo.getData(),new SingInDinerInfo(),false);
        if(dinerInfo==null){
            throw new ParameterException(ApiConstant.NO_LOGIN_CODE,ApiConstant.NO_LOGIN_MESSAGE);
        }
        return dinerInfo;

    }


    /*统计用户签到次数*/
    public Long getUserSignCount(String accessToken,String dateStr){

        SingInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);
        Date date = getDate(dateStr);
        String key = buildSignKey(dinerInfo.getId(),date);                  //根据日期创建Key
        Long execute = (Long) redisTemplate.execute((RedisCallback<Long>) a -> a.bitCount(key.getBytes()));
        return execute;
    }


    /*统计用户签到详细信息*/
    public Map<String,Boolean> getSignInfo(String accessToken,String dateStr){

        SingInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);
        Date date = getDate(dateStr);
        String key = buildSignKey(dinerInfo.getId(),date);                  //根据日期创建Key

        Map<String,Boolean> signMap = new TreeMap<>();
        int dayOfMouth = DateUtil.lengthOfMonth(
                DateUtil.month(date)+1,
                DateUtil.isLeapYear(DateUtil.dayOfYear(date))
        );  //获取月的天数,索引从0开始所以需要+1

        /*相当于 bitfield key get u当前天数 0 */
        BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMouth))
                .valueAt(0);
        List<Long> list = redisTemplate.opsForValue().bitField(key, bitFieldSubCommands);
        if(CollectionUtil.isEmpty(list)){
            return signMap;
        }

        long v = list.get(0) == null?0:list.get(0);//非空判断2
        /*提取为 yyyy-MM-dd true/false 的map统计*/
        for (int i = dayOfMouth;i>0;i--){
            LocalDateTime localDateTime = LocalDateTimeUtil.of(date).withDayOfMonth(i);     //当天日期
            boolean flag =  v>>1<<1 !=v;
            signMap.put(DateUtil.format(localDateTime,"yyyy-MM-dd"),flag);
            v >>=1;                     //右移减少一天
        }
        return signMap;

    }


    /**
     * 添加用户积分
     *
     * @param count         连续签到次数
     * @param signInDinerId 登录用户id
     * @return 获取的积分
     */
    private int addPoints(int count, Integer signInDinerId) {
        // 签到1天送10积分，连续签到2天送20积分，3天送30积分，4天以上均送50积分
        int points = 10;
        if (count == 2) {
            points = 20;
        } else if (count == 3) {
            points = 30;
        } else if (count >= 4) {
            points = 50;
        }
        // 调用积分接口添加积分
        // 构建请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        // 构建请求体（请求参数）
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("dinerId", signInDinerId);
        body.add("points", points);
        body.add("types", PointTypesConstant.sign.getType());
        HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
        // 发送请求
        ResponseEntity<ResultInfo> result = restTemplate.postForEntity(pointsServerName+"/points/save",
                entity, ResultInfo.class);
        AssertUtil.isTrue(result.getStatusCode() != HttpStatus.OK, "登录失败！");
        ResultInfo resultInfo = result.getBody();
        if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            // 失败了, 事物要进行回滚
            throw new ParameterException(resultInfo.getCode(), resultInfo.getMessage());
        }
        return points;
    }

}
